﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Best.SmartCms.Core.Utility;
using Newtonsoft.Json;

namespace Best.SmartCms.Utility
{
    public class SignatureService
    {
        /// <summary>
        /// 构造签名
        /// </summary>
        /// <param name="dic">请求参数</param>
        /// <param name="accessKey">签名公钥（来源系统）</param>
        /// <param name="secret">签名私钥</param>
        /// <param name="timestamp">时间搓</param>
        /// <param name="ignoreSign">忽略的参数</param>
        /// <returns></returns>
        public static string MakeSignature(Dictionary<string, string> dic, string accessKey, string secret, string timestamp, Collection<string> ignoreSign = null)
        {
            Dictionary<string, string> param = new Dictionary<string, string>();
            foreach (var item in dic)
            {
                param.Add(item.Key.ToLower(), item.Value);
            }

            if (param.ContainsKey("timestamp"))
            {
                param.Remove("timestamp");
            }

            if (param.ContainsKey("accesskey"))
            {
                param.Remove("accesskey");
            }

            param.Add("timestamp", timestamp);
            param.Add("accesskey", accessKey);
            var keys = param.Keys.ToList();

            //按字母大小写排序key
            keys.Sort(StringComparer.Ordinal);
            //以secret字符串开头，追加排序后参数名称和值，格式：secretkey1value1key2value2..
            StringBuilder query = new StringBuilder(secret);
            foreach (var key in keys)
            {
                param.TryGetValue(key, out var value);
                if ((ignoreSign == null || !ignoreSign.Contains(value)) && !string.IsNullOrWhiteSpace(key) && value!=null)
                {
                    query.Append(key).Append(value);
                }
            }

            NLogManager.LogTrace($"待签名字符串：{JsonConvert.SerializeObject(query.ToString())}");
            byte[] hash = EncryptHmac(query.ToString(), secret);
            return Byte2Hex(hash);
        }

        /// <summary>
        /// 构造签名
        /// </summary>
        /// <param name="param">请求参数</param>
        /// <param name="accessKey">签名公钥（来源系统）</param>
        /// <param name="secret">签名私钥</param>
        /// <param name="timestamp">时间搓</param>
        /// <param name="ignoreSign">忽略的参数</param>
        /// <returns></returns>
        public static string MakeSignature(Dictionary<string, string> param, string accessKey, string secret, out string timestamp, Collection<string> ignoreSign = null)
        {
            timestamp = CreateTimestamp();
            return MakeSignature(param, accessKey, secret, timestamp, ignoreSign);
        }

        /// <summary>
        /// 验证签名
        /// </summary>
        /// <param name="param">请求参数</param>
        /// <param name="accessKey">签名公钥（来源系统）</param>
        /// <param name="secret">签名私钥</param>
        /// <param name="timestamp">时间搓</param>
        /// <param name="timeOutSecond">过期时间秒 默认5分钟</param>
        /// <param name="token">验证的token</param>
        /// <param name="ignoreSign">忽略的参数</param>
        /// <returns></returns>
        public static bool CheckSignature(Dictionary<string, string> param, string accessKey, string secret, string timestamp, string token, long timeOutSecond = 300, Collection<string> ignoreSign = null)
        {
            var nowTimestamp = CreateTimestamp();
            var second = TimeStampDiffSecond(timestamp, nowTimestamp);
            if (timeOutSecond < second )
            {
                // 时间过期
                throw new Exception("请求链接已经超时.");
            }

            var fToken = MakeSignature(param, accessKey, secret, timestamp, ignoreSign);
            if (fToken != token)
            {
                throw new Exception("请求链接验证失败.");
            }

            return true;
        }

        /// <summary>
        /// 将API密钥的私钥 (secret) 作为key，生成被签名串的 HmacMD5签名
        /// </summary>
        /// <param name="data"></param>
        /// <param name="secret"></param>
        /// <returns></returns>
        private static byte[] EncryptHmac(String data, String secret)
        {
            HMACMD5 hmacMd5 = new HMACMD5(Encoding.UTF8.GetBytes(secret));
            byte[] textBytes = Encoding.UTF8.GetBytes(data);
            byte[] encodedTextBytes = hmacMd5.ComputeHash(textBytes);
            return encodedTextBytes;
        }

        /// <summary>
        /// 将签名得到的16字节依次转化为大写的16进制字符串，如果字符串长度为1，在前补0，结果为32位字符串
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private static string Byte2Hex(byte[] bytes)
        {
            StringBuilder sign = new StringBuilder();
            for (int i = 0; i < bytes.Length; i++)
            {
                var str = (bytes[i] & 0xFF).ToString("X2");
                String hex = str;
                if (hex.Length == 1)
                {
                    sign.Append("0");
                }
                sign.Append(hex.ToUpper());
            }
            return sign.ToString();
        }


        /// <summary>
        /// 创建时间戳
        /// </summary>
        /// <returns></returns>
        public static string CreateTimestamp()
        {
            var time = DateTime.Now;
            //Console.WriteLine("dt:{0}", time);
            DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1, 0, 0, 0, 0));
            long t = (time.Ticks - startTime.Ticks) / 10000;   //除10000调整为13位      
            return t.ToString();
        }
        /// <summary>        
        /// 时间戳转为C#格式时间        
        /// </summary>        
        /// <returns></returns>        
        public static DateTime TimeStampToDateTime(string timeStamp)
        {
            DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            long lTime = long.Parse(timeStamp + "0000");
            TimeSpan toNow = new TimeSpan(lTime);
            return dtStart.Add(toNow);
        }

        /// <summary>
        /// 间隔秒数
        /// </summary>
        /// <param name="beginTimeStamp"></param>
        /// <param name="toTimeStamp"></param>
        /// <returns></returns>
        public static double TimeStampDiffSecond(string beginTimeStamp, string toTimeStamp)
        {
            long lTime = long.Parse(beginTimeStamp + "0000");
            TimeSpan ts1 = new TimeSpan(lTime);
            long lTime2 = long.Parse(toTimeStamp + "0000");
            TimeSpan ts2 = new TimeSpan(lTime2);
            var spendTime = ts2 - ts1;
            var second = spendTime.TotalSeconds;
            return second;
        }
    }
}
